﻿using OpenTK;
using OpenTK.Graphics.OpenGL;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace LDLib
{
    /// <summary>
    /// Represents a text element that can be displayed on the GUI.
    /// Supports alignment and multiple lines (separated by '\n').
    /// </summary>
    public class GUIText : GUIElement
    {
        /// <summary>
        /// Alignment of the text
        /// </summary>
        public enum Align
        {
            Left,
            Center,
            Right
        }

        /// <summary>
        /// Line height
        /// </summary>
        private float lineHeight = 1f;

        /// <summary>
        /// Gets or sets the line height multiplier
        /// </summary>
        public float LineHeight
        {
            get
            {
                return lineHeight;
            }
            set
            {
                this.lineHeight = value;
                if (this.lineHeight <= 0f) lineHeight = 0.1f;
            }
        }

        /// <summary>
        /// Alignment of the text
        /// </summary>
        private Align align = Align.Left;

        /// <summary>
        /// Gets or sets the alignment of the text
        /// </summary>
        public Align Alignment
        {
            get { return align; }
            set
            {
                this.align = value;
                shouldUpdate = true;
            }
        }

        /// <summary>
        /// The text string
        /// </summary>
        private string text = "";

        /// <summary>
        /// Gets or sets the text to show
        /// </summary>
        public string Text
        {
            get
            {
                return text;
            }
            set
            {
                if (value == null) throw new ArgumentNullException("GUIText: Text cannot be null!");
                this.text = value;
                shouldUpdate = true;
            }
        }

        /// <summary>
        /// Font used for this text
        /// </summary>
        private BitmapFont font = null;

        /// <summary>
        /// Gets or sets the font used for this text
        /// </summary>
        public BitmapFont Font
        {
            get
            {
                return font;
            }
            set
            {
                if (value != null) this.font = value;
                else throw new ArgumentNullException("GUIText: Font cannot be null!");
            }
        }

        /// <summary>
        /// Mesh used to render the text
        /// </summary>
        private Mesh mesh;

        /// <summary>
        /// The 'smallest' corner of the screen rectangle
        /// </summary>
        private Vector2 scrRectMin = Vector2.Zero;

        /// <summary>
        /// Gets the 'smallest' corner of the screen rectangle
        /// </summary>
        public Vector2 ScreenRectMin { get { return scrRectMin; } }

        /// <summary>
        /// The 'largest' corner of the screen rectangle
        /// </summary>
        private Vector2 scrRectMax = Vector2.Zero;

        /// <summary>
        /// Gets the 'largest' corner of the screen rectangle
        /// </summary>
        public Vector2 ScreenRectMax { get { return scrRectMax; } }

        /// <summary>
        /// The 'smallest' corner of the clip rectangle
        /// </summary>
        private Vector2 clipRectMin = Vector2.Zero;

        /// <summary>
        /// Gets the 'smallest' corner of the clip rectangle
        /// </summary>
        public Vector2 ClipRectMin { get { return clipRectMin; } }

        /// <summary>
        /// The 'largest' corner of the clip rectangle
        /// </summary>
        private Vector2 clipRectMax = Vector2.Zero;

        /// <summary>
        /// Gets the 'largest' corner of the clip rectangle
        /// </summary>
        public Vector2 ClipRectMax { get { return clipRectMax; } }

        /// <summary>
        /// Indicates whether or not the mesh should be updated
        /// </summary>
        private bool shouldUpdate = false;

        /// <summary>
        /// Constructs a new GUI text
        /// </summary>
        /// <param name="font">Font to use</param>
        /// <param name="text">Text to show</param>
        public GUIText(BitmapFont font, string text)
        {
            if (font == null) throw new ArgumentNullException("GUIText: Font cannot be null!");
            if (text == null) throw new ArgumentNullException("GUIText: Text cannot be null!");

            this.mesh = new Mesh();
            this.font = font;
            this.Text = text;
        }

        /// <summary>
        /// Checks whether or not the given X, Y coordinate is
        /// inside the screen rectangle of this text element
        /// </summary>
        /// <param name="x">X screen coordinate</param>
        /// <param name="y">Y screen coordinate</param>
        /// <returns>True if the screen rectangle contains the coordinate, false otherwise</returns>
        public bool IsAt(float x, float y)
        {
            if (x > scrRectMin.X &&
               x < scrRectMax.X &&
               y > scrRectMin.Y &&
               y < scrRectMax.Y)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Refreshes the mesh used to render the text
        /// </summary>
        private void UpdateMesh()
        {
            mesh.Vertices.Clear();
            mesh.Indices.Clear();

            int currentLine = 0;
            float totalWidth = 0f;
            float totalHeight = Math.Abs(font.size);

            float lastx = 0f;
            for (int i = 0; i < text.Length; ++i)
            {
                if (text[i] == '\n')
                {
                    currentLine++;
                    lastx = 0f;
                    continue;
                }

                Mesh charMesh = font.meshes[(int)text[i]];

                foreach (int j in charMesh.Indices)
                {
                    mesh.Indices.Add(j + mesh.Vertices.Count);
                }

                Vertex v0 = charMesh.Vertices[0];
                Vertex v1 = charMesh.Vertices[1];
                Vertex v2 = charMesh.Vertices[2];
                Vertex v3 = charMesh.Vertices[3];
                float width = v1.position.X - v0.position.X;
                float height = v2.position.Y - v0.position.Y;
                v0.position.X = lastx;
                v1.position.X = lastx + width;
                v2.position.X = lastx;
                v3.position.X = lastx + width;

                float lHeight = height;
                v0.position.Y -= lHeight * currentLine * lineHeight;
                v1.position.Y -= lHeight * currentLine * lineHeight;
                v2.position.Y -= lHeight * currentLine * lineHeight;
                v3.position.Y -= lHeight * currentLine * lineHeight;

                lastx = lastx + width;
                mesh.Vertices.Add(v0);
                mesh.Vertices.Add(v1);
                mesh.Vertices.Add(v2);
                mesh.Vertices.Add(v3);

                totalWidth += (float)font.chars[(int)text[i]].width / font.width;
            }

            if (align == Align.Center)
            {
                for (int i = 0; i < mesh.Vertices.Count; ++i)
                {
                    Vertex v = mesh.Vertices[i];
                    v.position.X -= totalWidth / 2f;
                    mesh.Vertices[i] = v;
                }
            }
            else if (align == Align.Right)
            {
                for (int i = 0; i < mesh.Vertices.Count; ++i)
                {
                    Vertex v = mesh.Vertices[i];
                    v.position.X -= totalWidth;
                    mesh.Vertices[i] = v;
                }
            }

            // Update rectangle
            clipRectMin = new Vector2(10000f, 10000f);
            clipRectMax = new Vector2(-10000f, -10000f);
            foreach (Vertex v in mesh.Vertices)
            {
                if (v.position.X < clipRectMin.X) clipRectMin.X = v.position.X;
                if (v.position.Y < clipRectMin.Y) clipRectMin.Y = v.position.Y;
                if (v.position.X > clipRectMax.X) clipRectMax.X = v.position.X;
                if (v.position.Y > clipRectMax.Y) clipRectMax.Y = v.position.Y;
            }

            scrRectMin.X = clipRectMin.X * (float)Properties.ScreenWidth / 2f + (float)Properties.ScreenWidth / 2f;
            scrRectMin.Y = clipRectMin.Y * (float)Properties.ScreenHeight / 2f + (float)Properties.ScreenHeight / 2f;
            scrRectMax.X = clipRectMax.X * (float)Properties.ScreenWidth / 2f + (float)Properties.ScreenWidth / 2f;
            scrRectMax.Y = clipRectMax.Y * (float)Properties.ScreenHeight / 2f + (float)Properties.ScreenHeight / 2f;

            mesh.ShouldReload = true;
        }

        /// <summary>
        /// Sets the screen position of the text
        /// </summary>
        /// <param name="pos">Position of the text on the screen</param>
        public void SetPosition(Vector2 pos)
        {
            this.Position = pos;
        }

        /// <summary>
        /// Renders the text element
        /// </summary>
        internal override void Render()
        {
            if (!enabled) return;
            if (mesh.VBO == -1) mesh.SetupMesh();
            if (shouldUpdate)
            {
                UpdateMesh();
                shouldUpdate = false;
            }

            Shader.currentShader.Set("world", matrix);
            Shader.currentShader.Set("diffuseColor", color);
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, font.texture);
            Shader.currentShader.Set("colorMap", 0);
            mesh.RenderMesh();
        }
    }
}
